package org.wf.jwtp.provider;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.util.Assert;
import org.wf.jwtp.util.TokenUtil;

import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * jdbc存储token的实现
 * Created by wangfan on 2018-12-28 下午 1:00.
 */
public class JdbcTokenStore implements TokenStore {
	protected final Log logger = LogFactory.getLog(this.getClass());
	private final JdbcTemplate jdbcTemplate;
	private RowMapper<Token> rowMapper = new TokenRowMapper();

	private static final String UPDATE_FIELDS = "access_token, user_id, permissions, roles, refresh_token, expire_time";

	private static final String BASE_SELECT =
			"select token_id, " + UPDATE_FIELDS + ", create_time, update_time from oauth_token";

	private static final String SQL_SELECT_BY_TOKEN = BASE_SELECT + " where user_id = ? and access_token = ?";

	/**
	 * 检索token根据创建时间
	 */
	private static final String SQL_SELECT_BY_USER_ID = BASE_SELECT + " where user_id = ? order by create_time";

	/**
	 * 这个原理应该是支持一个用户多个token，在我们项目中只允许单登录，所以此处要做下处理
	 */
	private static final String SQL_INSERT = "insert into oauth_token (" + UPDATE_FIELDS + ") values (?,?,?,?,?,?)";

	private static final String SQL_UPDATE =
			"update oauth_token set " + UPDATE_FIELDS.replaceAll(", ", "=?, ") + "=? where token_id = ?";

	private static final String SQL_UPDATE_PERMS = "update oauth_token set permissions = ? where user_id = ?";

	private static final String SQL_UPDATE_ROLES = "update oauth_token set roles = ? where user_id = ?";

	private static final String SQL_DELETE = "delete from oauth_token where user_id = ? and access_token = ?";

	private static final String SQL_DELETE_BY_USER_ID = "delete from oauth_token where user_id = ?";

	private static final String SQL_SELECT_KEY = "select token_key from oauth_token_key";

	private static final String SQL_INSERT_KEY = "insert into oauth_token_key (token_key) values (?)";

	public JdbcTokenStore(DataSource dataSource) {
		Assert.notNull(dataSource, "DataSource required");
		this.jdbcTemplate = new JdbcTemplate(dataSource);
	}

	@Override
	public String getTokenKey() {
		String tokenKey = null;
		try {
			//此处如有多个，如何处理？
			tokenKey = jdbcTemplate.queryForObject(SQL_SELECT_KEY, String.class);
		} catch (EmptyResultDataAccessException e) {
		}
		//如果为空，那么就更新
		if (tokenKey == null || tokenKey.trim().isEmpty()) {
			tokenKey = TokenUtil.getHexKey();
			jdbcTemplate.update(SQL_INSERT_KEY, tokenKey);
		}
		return tokenKey;
	}

	@Override
	public Token createNewToken(String userId, String[] permissions, String[] roles) {
		return createNewToken(userId, permissions, roles, TokenUtil.DEFAULT_EXPIRE);
	}

	@Override
	public Token createNewToken(String userId, String[] permissions, String[] roles, long expire) {
		//从数据库获取token
		String tokenKey = getTokenKey();

		logger.debug("-------------------------------------------");
		logger.debug("构建token使用tokenKey：" + tokenKey);
		logger.debug("-------------------------------------------");
		//创建token，核心数据是userid,超期时间，key
		Token token = TokenUtil.buildToken(userId, expire, TokenUtil.parseHexKey(tokenKey));
		token.setPermissions(permissions);
		token.setRoles(roles);
		//然后进行保存
		int flag = storeToken(token);
		//保存成功后，判定当前用户具有几个token。
		if (flag > 0) {
			if (Config.getInstance().getMaxToken() != null && Config.getInstance().getMaxToken() != -1) {
				//todo 建议此处抽取出来，封装为方法，假如这儿全局的配置经过了修改，那么
				List<Token> userTokens = findTokensByUserId(userId);
				if (userTokens.size() > Config.getInstance().getMaxToken()) {
					for (int i = 0; i < userTokens.size() - Config.getInstance().getMaxToken(); i++) {
						//最早的token先删除
						removeToken(userId, userTokens.get(i).getAccessToken());
					}
				}
			}
			return token;
		}
		return null;
	}


	/**
	 * 保存令牌授权，主要用户登录的时候。
	 * @param token
	 * @return
	 */
	@Override
	public int storeToken(Token token) {
		List<Object> objects = genFieldsForUpdate(token);
		//注意生成的一定要有顺序
		return jdbcTemplate.update(SQL_INSERT, listToArray(objects));
	}

	@Override
	public Token findToken(String userId, String access_token) {
		try {
			return jdbcTemplate.queryForObject(SQL_SELECT_BY_TOKEN, rowMapper, userId, access_token);
		} catch (EmptyResultDataAccessException e) {
		}
		//不建议在异常里return 吗？
		return null;
	}

	@Override
	public List<Token> findTokensByUserId(String userId) {
		try {
			return jdbcTemplate.query(SQL_SELECT_BY_USER_ID, rowMapper, userId);
		} catch (EmptyResultDataAccessException e) {
		}
		return null;
	}

	@Override
	public int removeToken(String userId, String access_token) {
		return jdbcTemplate.update(SQL_DELETE, userId, access_token);
	}

	@Override
	public int removeTokensByUserId(String userId) {
		return jdbcTemplate.update(SQL_DELETE_BY_USER_ID, userId);
	}

	@Override
	public int updateRolesByUserId(String userId, String[] roles) {
		Object[] objects = new Object[2];
		try {
			com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
			objects[0] = mapper.writeValueAsString(roles);
		} catch (Exception e) {
			e.printStackTrace();
		}
		objects[1] = userId;
		return jdbcTemplate.update(SQL_UPDATE_ROLES, objects);
	}

	@Override
	public int updatePermissionsByUserId(String userId, String[] permissions) {
		Object[] objects = new Object[2];
		try {
			com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
			objects[0] = mapper.writeValueAsString(permissions);
		} catch (Exception e) {
			e.printStackTrace();
		}
		objects[1] = userId;
		return jdbcTemplate.update(SQL_UPDATE_PERMS, objects);
	}

	/**
	 * 构建插入的字段
	 * @param token
	 * @return
	 */
	private List<Object> genFieldsForUpdate(Token token) {
		List<Object> objects = new ArrayList();
		objects.add(token.getAccessToken());
		objects.add(token.getUserId());
		String permJson = null;
		//permissions
		try {
			com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
			permJson = mapper.writeValueAsString(token.getPermissions());
		} catch (Exception e) {
			e.printStackTrace();
		}
		objects.add(permJson);
		//roles
		String roleJson = null;
		try {
			com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
			roleJson = mapper.writeValueAsString(token.getRoles());
		} catch (Exception e) {
			e.printStackTrace();
		}
		objects.add(roleJson);
		// objects.add(token.getTokenKey());
		objects.add(token.getRefreshToken());
		objects.add(token.getExpireTime());
		return objects;
	}

	private Object[] listToArray(List<Object> list) {
		if (list == null) {
			return null;
		}
		Object[] objects = new Object[list.size()];
		for (int i = 0; i < list.size(); i++) {
			objects[i] = list.get(i);
		}
		return objects;
	}


	private static class TokenRowMapper implements RowMapper<Token> {

		@Override
		public Token mapRow(ResultSet rs, int rowNum) throws SQLException {
			int token_id = rs.getInt("token_id");
			String access_token = rs.getString("access_token");
			String user_id = rs.getString("user_id");
			String permissions = rs.getString("permissions");
			String roles = rs.getString("roles");
			// String token_key = rs.getString("token_key");
			String refresh_token = rs.getString("refresh_token");
			Date expire_time = rs.getDate("expire_time");
			Date create_time = rs.getDate("create_time");
			Date update_time = rs.getDate("update_time");
			Token token = new Token();
			token.setTokenId(token_id);
			token.setAccessToken(access_token);
			token.setUserId(user_id);
			// token.setTokenKey(token_key);
			token.setRefreshToken(refresh_token);
			token.setExpireTime(expire_time);
			token.setCreateTime(create_time);
			token.setUpdateTime(update_time);
			if (permissions != null) {
				try {
					com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
					token.setPermissions(listToArray((List<String>) mapper.readValue(permissions,
							mapper.getTypeFactory().constructParametricType(ArrayList.class, String.class))));
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			if (roles != null) {
				try {
					com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
					token.setRoles(listToArray((List<String>) mapper.readValue(roles,
							mapper.getTypeFactory().constructParametricType(ArrayList.class, String.class))));
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			return token;
		}

		private String[] listToArray(List<String> list) {
			if (list == null) {
				return null;
			}
			String[] objects = new String[list.size()];
			for (int i = 0; i < list.size(); i++) {
				objects[i] = list.get(i);
			}
			return objects;
		}
	}
}
